001 /*
002 * Copyright (c) 2005 Stephen J. McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.metro.tools;
020
021 import java.beans.IntrospectionException;
022 import java.beans.Encoder;
023 import java.beans.Expression;
024 import java.beans.DefaultPersistenceDelegate;
025 import java.io.File;
026 import java.io.IOException;
027 import java.net.URI;
028 import java.net.URISyntaxException;
029
030 import net.dpml.component.Directive;
031 import net.dpml.component.ActivationPolicy;
032
033 import net.dpml.library.info.Scope;
034 import net.dpml.library.Resource;
035
036 import net.dpml.metro.data.ComponentDirective;
037 import net.dpml.metro.data.ContextDirective;
038 import net.dpml.metro.data.CategoriesDirective;
039 import net.dpml.metro.info.LifestylePolicy;
040 import net.dpml.metro.info.CollectionPolicy;
041 import net.dpml.metro.info.PartReference;
042 import net.dpml.metro.info.Type;
043 import net.dpml.metro.info.EntryDescriptor;
044 import net.dpml.metro.builder.ComponentTypeDecoder;
045 import net.dpml.metro.data.DefaultComposition;
046
047 import net.dpml.lang.Classpath;
048 import net.dpml.lang.Part;
049 import net.dpml.lang.Info;
050
051 import net.dpml.transit.monitor.LoggingAdapter;
052
053 import net.dpml.tools.tasks.PartTask;
054
055 import org.apache.tools.ant.BuildException;
056 import org.apache.tools.ant.Project;
057 import org.apache.tools.ant.AntClassLoader;
058 import org.apache.tools.ant.types.Path;
059
060 /**
061 * Task that handles the construction of a serialized container part.
062 *
063 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
064 * @version 1.1.0
065 */
066 public class ComponentBuilderTask extends PartTask implements PartReferenceBuilder
067 {
068 private static final String NAMESPACE = "link:xsd:dpml/lang/dpml-component#1.0";
069
070 private static final ComponentTypeDecoder COMPONENT_TYPE_DECODER =
071 new ComponentTypeDecoder();
072
073 private URI m_uri;
074 private String m_key;
075 private boolean m_embedded = false;
076 private String m_name;
077 private String m_classname;
078 private LifestylePolicy m_lifestyle;
079 private CollectionPolicy m_collection;
080 private ActivationPolicy m_activation = ActivationPolicy.SYSTEM;
081 private CategoriesDataType m_categories;
082 private ContextDataType m_context;
083 private PartsDataType m_parts;
084 private File m_output;
085 private Type m_type;
086 private URI m_extends;
087 private boolean m_alias = false;
088
089 /**
090 * Set the part key.
091 * @param key the key
092 */
093 public void setKey( String key )
094 {
095 m_key = key;
096 }
097
098 /**
099 * Set the alias production flag value.
100 * @param alias true if alias production is requested
101 */
102 public void setAlias( boolean alias )
103 {
104 m_alias = alias;
105 }
106
107 /**
108 * Set the extends uri feature.
109 * @param uri the uri from which the component extends
110 */
111 public void setExtends( URI uri )
112 {
113 m_extends = uri;
114 }
115
116 /**
117 * Set the embedded component flag.
118 * @param flag true if embedded
119 */
120 //public void setEmbedded( boolean flag )
121 //{
122 // m_embedded = flag;
123 //}
124
125 /**
126 * Set the component name.
127 * @param name the component name
128 */
129 public void setName( String name )
130 {
131 m_name = name;
132 }
133
134 /**
135 * Set the component classname.
136 * @param classname the component type classname
137 */
138 public void setType( String classname )
139 {
140 m_classname = classname;
141 }
142
143 /**
144 * Set the lifestyle policy vlaue.
145 * @param policy the lifestyle policy
146 */
147 public void setLifestyle( String policy )
148 {
149 m_lifestyle = LifestylePolicy.parse( policy );
150 }
151
152 /**
153 * Set the gabage collection policy value.
154 * @param policy the collection policy
155 */
156 public void setCollection( String policy )
157 {
158 m_collection = CollectionPolicy.parse( policy );
159 }
160
161 /**
162 * Set the activation policy value.
163 * @param policy the activation policy
164 */
165 public void setActivation( String policy )
166 {
167 m_activation = ActivationPolicy.parse( policy );
168 }
169
170 /**
171 * Create a new categories data type.
172 * @return the categories datatype
173 */
174 public CategoriesDataType createCategories()
175 {
176 if( m_categories == null )
177 {
178 m_categories = new CategoriesDataType();
179 return m_categories;
180 }
181 else
182 {
183 final String error =
184 "Illegal attempt to create a duplicate categories declaration.";
185 throw new BuildException( error, getLocation() );
186 }
187 }
188
189 /**
190 * Create a new context data type.
191 * @return the context datatype
192 */
193 public ContextDataType createContext()
194 {
195 if( null == m_context )
196 {
197 m_context = new ContextDataType();
198 return m_context;
199 }
200 else
201 {
202 final String error =
203 "Illegal attempt to create a duplicate context declaration.";
204 throw new BuildException( error, getLocation() );
205 }
206 }
207
208 /**
209 * Create a new part datatype.
210 * @return a new part datatype
211 */
212 public PartsDataType createParts()
213 {
214 if( m_parts == null )
215 {
216 m_parts = new PartsDataType( this );
217 return m_parts;
218 }
219 else
220 {
221 final String error =
222 "Illegal attempt to create a duplicate parts element.";
223 throw new BuildException( error, getLocation() );
224 }
225 }
226
227 /**
228 * Build the plugin definition.
229 * @param resource the project resource definition
230 * @return the part definition
231 */
232 protected Part build( Resource resource )
233 {
234 try
235 {
236 Info info = getInfo( resource );
237 Classpath classpath = getClasspath( resource );
238 ClassLoader classloader = createClassLoader();
239 ComponentDirective profile = buildComponentDirective( classloader );
240 return new DefaultComposition(
241 new LoggingAdapter( "depot" ),
242 info, classpath, null, profile );
243 }
244 catch( Throwable e )
245 {
246 final String error =
247 "Internal error while attempting to build an external part defintion."
248 + "\nResource: " + resource;
249 throw new BuildException( error, e, getLocation() );
250 }
251 }
252
253 /**
254 * Return the runtime classloader.
255 * @return the classloader
256 */
257 protected ClassLoader createClassLoader()
258 {
259 Project project = getProject();
260 Path path = getContext().getPath( Scope.RUNTIME );
261 File classes = getContext().getTargetClassesMainDirectory();
262 path.createPathElement().setLocation( classes );
263 ClassLoader parentClassLoader = getClass().getClassLoader();
264 return new AntClassLoader( parentClassLoader, project, path, true );
265 }
266
267 //---------------------------------------------------------------------
268 // Builder
269 //---------------------------------------------------------------------
270
271 /**
272 * Return a uri identitifying the builder.
273 *
274 * @return the builder uri
275 */
276 public URI getBuilderURI()
277 {
278 return PART_BUILDER_URI;
279 }
280
281 //---------------------------------------------------------------------
282 // PartBuilder
283 //---------------------------------------------------------------------
284
285 /**
286 * Return a urn identitifying the part handler for this builder.
287 *
288 * @return a strategy uri
289 */
290 public URI getPartHandlerURI()
291 {
292 return PART_HANDLER_URI;
293 }
294
295 /**
296 * Build the part.
297 * @param classloader the classloader
298 * @return the part
299 * @exception IntrospectionException if an error occurs while introspecting the component class
300 * @exception IOException if an I/O error occurs
301 * @exception ClassNotFoundException if the component class cannot be found
302 */
303 public Directive buildDirective( ClassLoader classloader )
304 throws IntrospectionException, IOException, ClassNotFoundException
305 {
306 String classname = getClassname();
307 Type type = loadType( classloader, classname );
308 return buildComponentDirective( type, classloader );
309 }
310
311 //---------------------------------------------------------------------
312 // PartReferenceBuilder
313 //---------------------------------------------------------------------
314
315 /**
316 * Return the part key.
317 *
318 * @return the key
319 */
320 public String getKey()
321 {
322 if( null == m_key )
323 {
324 final String error =
325 "Missing key attribute for nested part reference.";
326 throw new BuildException( error );
327 }
328 else
329 {
330 return m_key;
331 }
332 }
333
334 /**
335 * Build a part reference.
336 * @param classloader the classloader
337 * @param type the component type
338 * @return the part reference
339 * @exception IntrospectionException if an error occurs while introspecting the component class
340 * @exception IOException if an I/O error occurs
341 * @exception ClassNotFoundException if the component class cannot be found
342 */
343 public PartReference buildPartReference( ClassLoader classloader, Type type )
344 throws IntrospectionException, IOException, ClassNotFoundException
345 {
346 String key = getKey();
347 Directive directive = buildComponentDirective( type, classloader );
348 return new PartReference( key, directive );
349 }
350
351 //---------------------------------------------------------------------
352 // impl
353 //---------------------------------------------------------------------
354
355 private ComponentDirective buildComponentDirective( Type type, ClassLoader classloader )
356 throws IntrospectionException, IOException, ClassNotFoundException
357 {
358 return buildComponentDirective( classloader );
359 }
360
361 private ComponentDirective buildComponentDirective( ClassLoader classloader )
362 throws IntrospectionException, IOException, ClassNotFoundException
363 {
364 String classname = getClassname();
365 Type type = loadType( classloader, classname );
366 String id = getName( type.getInfo().getName() );
367 log( "creating [" + id + "] using [" + classname + "]" );
368
369 LifestylePolicy lifestyle = getLifestylePolicy();
370 CollectionPolicy collection = getCollectionPolicy( type );
371 ActivationPolicy activation = getActivationPolicy();
372 CategoriesDirective categories = getCategoriesDirective();
373 ContextDirective context = getContextDirective( classloader, type );
374 PartReference[] parts = getParts( classloader );
375 URI base = getBaseURI();
376
377 //
378 // return the component profile
379 //
380
381 return new ComponentDirective(
382 id, activation, collection, lifestyle, classname, categories,
383 context, parts, base );
384 }
385
386 private URI getBaseURI()
387 {
388 return m_extends;
389 }
390
391 private Type loadType( ClassLoader classloader, String classname )
392 {
393 if( null == classloader )
394 {
395 throw new NullPointerException( "classloader" );
396 }
397 if( null == classname )
398 {
399 throw new NullPointerException( "classname" );
400 }
401 try
402 {
403 Resource resource = getResource();
404 Class c = classloader.loadClass( classname );
405 return COMPONENT_TYPE_DECODER.loadType( c, resource );
406 }
407 catch( Throwable e )
408 {
409 final String error =
410 "Unexpected error occured while attempting to load type ["
411 + classname
412 + "]";
413 throw new BuildException( error, e, getLocation() );
414 }
415 }
416
417 /**
418 * Return the component name.
419 * @param typeName the component type name (used as a default)
420 * @return the name
421 */
422 protected String getName( String typeName )
423 {
424 if( null == m_name )
425 {
426 if( null != m_key )
427 {
428 return m_key;
429 }
430 else
431 {
432 return typeName;
433 }
434 }
435 else
436 {
437 return m_name;
438 }
439 }
440
441 /**
442 * Return the component classname.
443 * @return the classname
444 */
445 protected String getClassname()
446 {
447 if( null == m_classname )
448 {
449 return Object.class.getName();
450 }
451 else
452 {
453 return m_classname;
454 }
455 }
456
457 /**
458 * Return the lifestyle policy declared relative to usage.
459 * If undefined then default to the lifestyle declared by the component type.
460 * Lifestyle policies that may be declared under the 'lifestyle' attribute of
461 * a component are 'transient', 'thread' or 'singleton'. If 'transient' is supplied
462 * the assigned lefestyle policy is InfoDescriptor.TRANSIENT resulting in the
463 * creation of a new instance per request. If 'thread' is declared the assigned
464 * lifestyle policy shall be InfoDescriptor.THREAD in which case a supplied
465 * instance will be reused for all requests within the same thread of execution.
466 * If the supplied policy is 'singleton' then the established instance will be
467 * shared across consumers referencing the component.
468 *
469 * @return the lifestyle policy
470 */
471 public LifestylePolicy getLifestylePolicy()
472 {
473 return m_lifestyle;
474 }
475
476 /**
477 * Return the collection policy.
478 * @param type the component type from which the default collection
479 * policy can be resolved if needed
480 * @return the collection policy
481 */
482 public CollectionPolicy getCollectionPolicy( Type type )
483 {
484 if( null == m_collection )
485 {
486 return type.getInfo().getCollectionPolicy();
487 }
488 else
489 {
490 return m_collection;
491 }
492 }
493
494 /**
495 * Return the activation policy.
496 * @return the component activation policy
497 */
498 public ActivationPolicy getActivationPolicy()
499 {
500 return m_activation;
501 }
502
503 /**
504 * Return the context directive.
505 * @param classloader the classloader to use
506 * @param type the component type
507 * @return the context directive
508 * @exception IntrospectionException if a class introspection error occurs
509 * @exception IOException if an I/O error occurs
510 * @exception ClassNotFoundException if a component context class cannont be found
511 */
512 private ContextDirective getContextDirective( ClassLoader classloader, Type type )
513 throws IntrospectionException, IOException, ClassNotFoundException
514 {
515 String name = getName( type.getInfo().getName() );
516 String classname = type.getInfo().getClassname();
517 ContextDirective context = createContextDirective( classloader, type );
518 if( null == context )
519 {
520 // return m_profile.getContextDirective();
521 return null;
522 }
523
524 //
525 // validate that the context directives are declared
526 // and if not - throw an exception
527 //
528
529 EntryDescriptor[] entries = type.getContextDescriptor().getEntryDescriptors();
530 for( int i=0; i<entries.length; i++ )
531 {
532 EntryDescriptor entry = entries[i];
533 String key = entry.getKey();
534
535 Directive part = context.getPartDirective( key );
536 if( entry.isRequired() && ( null == part ) )
537 {
538 final String error =
539 "The component model ["
540 + name
541 + "] referencing the component type ["
542 + classname
543 + "] does not declare a context entry for the non-optional entry ["
544 + key
545 + "].";
546 throw new ConstructionException( error, getLocation() );
547 }
548 }
549
550 //
551 // we are ship-shape
552 //
553
554 return context;
555 }
556
557 private ContextDirective createContextDirective( ClassLoader classloader, Type type )
558 throws IntrospectionException, IOException, ClassNotFoundException
559 {
560 if( null == m_context )
561 {
562 return null;
563 }
564 else
565 {
566 return m_context.getContextDirective( classloader, type );
567 }
568 }
569
570 private CategoriesDirective getCategoriesDirective()
571 {
572 if( null == m_categories )
573 {
574 //return m_profile.getCategoriesDirective();
575 return null;
576 }
577 else
578 {
579 return m_categories.getCategoriesDirective();
580 }
581 }
582
583 private PartReference[] getParts( ClassLoader classloader )
584 throws IntrospectionException, IOException
585 {
586 if( null != m_parts )
587 {
588 try
589 {
590 return m_parts.getParts( classloader, null );
591 }
592 catch( ClassNotFoundException cnfe )
593 {
594 final String error =
595 "Unable to load a class referenced by a nested part within a component type.";
596 throw new BuildException( error, cnfe );
597 }
598 }
599 else
600 {
601 return new PartReference[0];
602 }
603 }
604
605 /**
606 * Utility class used to handle uri persistence.
607 */
608 public static class URIPersistenceDelegate extends DefaultPersistenceDelegate
609 {
610 /**
611 * Return an expressio to create a uri.
612 * @param old the old value
613 * @param encoder the encoder
614 * @return the expression
615 */
616 public Expression instantiate( Object old, Encoder encoder )
617 {
618 URI uri = (URI) old;
619 String spec = uri.toString();
620 Object[] args = new Object[]{spec};
621 return new Expression( old, old.getClass(), "new", args );
622 }
623 }
624
625 /**
626 * Constant controller uri.
627 */
628 public static final URI PART_HANDLER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.0.1" );
629
630 /**
631 * Constant strategy builder uri.
632 */
633 public static final URI STRATEGY_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.0.1" );
634
635 /**
636 * Constant builder uri.
637 */
638 public static final URI PART_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-tools#1.1.0" );
639
640 /**
641 * Utility function to create a static uri.
642 * @param spec the uri spec
643 * @return the uri
644 */
645 protected static URI setupURI( String spec )
646 {
647 try
648 {
649 return new URI( spec );
650 }
651 catch( URISyntaxException ioe )
652 {
653 return null;
654 }
655 }
656 }